﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ESP
{
    public class SubPopulation /*: Cloneable*/
    {
        //protected:
        private Neuron[] pop; // Neuron
        private int geneCount;

        private static RandomCauchy rndCauchy = new RandomCauchy();

        public SubPopulation(int populationCount, Network net)
        {
            this.geneCount = net.getGeneSize();
            pop = new Neuron[populationCount];
            evolvable = true;
            numBreed = (int) populationCount/4; //we leave subPop::create to 
            // actually create the neurons.  This way neurons are not created
            // when we initially create a Network
            // for (int i = 0; i < getGeneCount(); ++i)
            // pop[i] = new neuron();
        }

/*	
	public Object clone() {
		try {
			SubPopulation rc = (SubPopulation)super.clone();
			rc.pop = (Neuron[])pop.clone();
			for( int i=0; i < rc.pop.Length; i++ ) {
				rc.pop[i] = (Neuron)rc.pop[i].clone();
			}
			return rc;
		} catch( CloneNotSupportedException e ) {
			return null;
		}
	}
*/

        public SubPopulation(String fname)
        {
            //try {
            //    DataInputStream fptr = new DataInputStream( new FileInputStream( new File( fname ) ) );
            //    int populationSize = fptr.readInt();
            //    geneCount = fptr.readInt();
            //    pop = new Neuron[populationSize];
            //    for( int i=0; i < pop.Length; i++ ) {
            //        pop[i] = new Neuron(getGeneCount());
            //    }
            //    evolvable = true;
            //    numBreed = (int) populationSize/4;   //we leave subPop::create to 

            //    fptr.close();
            //} catch( IOException e ) {
            //    Console.WriteLine(" Error - cannot open " + fname);
            //    Console.WriteLine( e );
            //    System.exit(1);
            //}

            throw new NotImplementedException();
        }

        public int getPopulationSize()
        {
            return pop.Length;
        }

        public int getGeneCount()
        {
            return geneCount;
        }

/*	
	public void addNeuron() {
		Neuron[] temp = new Neuron[ pop.Length+1 ];
		for( int i = 0; i < pop.Length; i++ ) {
			temp[i] = pop[i];
		}
		temp[pop.Length] = new Neuron( getGeneCount() );
		pop = temp;
	}
	
	public void removeNeuron( int index ) {
		Neuron[] temp = new Neuron[ pop.Length-1 ];
		for( int i = 0; i < index; i++ ) {
			temp[i] = pop[i];
		}
		for( int i = index+1; i < pop.Length; i++ ) {
			temp[i-1] = pop[i];
		}
		pop = temp;
	}
	
	private void setNeuron( int index, Neuron n ) {
		pop[index] = (Neuron)n.clone();
	}
*/

        public Neuron getNeuron(int index)
        {
            return pop[index];
        }

        //  SubPopulation(const SubPopulation &s);
//---------------------------------------------------------------------
// create the neurons, initial their weights, and put them in the subpop.
        public void create()
        {
            // creates a random subpopulation of neurons
            if (evolvable)
            {
                for (int i = 0; i < pop.Length; ++i)
                {
                    pop[i] = new Neuron(getGeneCount());
                    pop[i].create();
                }
            }
        }

//----------------------------------------------------------------------
// reset fitness and test vals of all neurons
        public void evalReset()
        {
            for (int i = 0; i < pop.Length; ++i)
            {
                pop[i].fitness = 0;
                pop[i].tests = 0;
            }
        }

//----------------------------------------------------------------------
// select a neuron at random
        public Neuron selectNeuron()
        {
            int random = Math.Abs(RandomSingleton.getInstance().Next());
            int length = pop.Length;
            int index = random%length;
            return pop[index];
        }

//----------------------------------------------------------------------
// normalize the neuron fitnesses 
        public void average()
        {
            for (int i = 0; i < pop.Length; ++i)
            {
                if (pop[i].tests != 0)
                {
                    pop[i].fitness = pop[i].fitness/pop[i].tests;
                }
                else
                {
                    pop[i].fitness = 0;
                }
            }
        }

//----------------------------------------------------------------------
// sort the neurons in each subpop using quicksort.
        private static readonly IComparer minimize_fit = new MinimizeFit();
        private static readonly IComparer maximize_fit = new MaximizeFit();

        public void qsortNeurons()
        {
            if (EspAlgorithm.MIN)
            {
                Array.Sort(pop, minimize_fit);
            }
            else
            {
                Array.Sort(pop, maximize_fit);
            }
        }

//----------------------------------------------------------------------
// recombine neurons with members of their subpop using crossover.
        public void recombine()
        {
            for (int i = 0; i < numBreed; ++i)
            {
                int mate = findMate(i);
                crossover(pop[i].weight, pop[mate].weight,
                    pop[pop.Length - (1 + i*2)].weight,
                    pop[pop.Length - (2 + i*2)].weight);
            }
        }

//----------------------------------------------------------------------
// mutate half of the neurons with cauchy noise.
        public void mutate(double rate)
        {
            for (int i = numBreed*2; i < pop.Length; ++i)
            {
                if (RandomSingleton.getInstance().NextDouble() < rate)
                {
                    pop[i].weight[Math.Abs(RandomSingleton.getInstance().Next())%getGeneCount()] +=
                        rndCauchy.randomFunction(0.3);
                }
            }
        }

//---------------------------------------------------------------------
// used to perform "delta-coding" like 
        public void deltify(Neuron bestNeuron)
        {
            //  neuron tmp = *pop[0];
            for (int i = 0; i < pop.Length; ++i)
            {
                pop[i].perturb(bestNeuron); // make each neuron a perturbation of the
                // neuron in the best network that 
                // corresponds to that subpop
                //delete pop[i];
                //if(i > (pop.Length/2))
                //  pop[i] = bestNeuron.perturb();    
                //else
                //pop[i] = tmp.perturb();
            }
        }

//	public void save(String fname);
// add a weight at position 'locus' for all neuron in the subpop.
        public void addConnection(int locus)
        {
            // TODO: Make more efficient
            for (int i = 0; i < pop.Length; ++i)
            {
                pop[i].weight = doubleArrayInsert(pop[i].weight, 1.0, locus);
            }
        }

//----------------------------------------------------------------------
// opposite of addConnection.
        public void removeConnection(int locus)
        {
            if (locus < pop.Length)
            {
                for (int i = 0; i < pop.Length; ++i)
                {
                    pop[i].weight = doubleArrayRemove(pop[i].weight, locus);
                }
            }
        }

        public void saveBin(String fname)
        {
            //try {
            //    DataOutputStream dos = new DataOutputStream( new FileOutputStream( fname ) );
            //    dos.writeInt( pop.Length );
            //    dos.writeInt( getGeneCount() );

            //    for( int i = 0; i < pop.Length; ++i ) {
            //        for( int j = 0; j < getGeneCount(); ++j ) {
            //            dos.writeDouble(pop[i].weight[j]);
            //        }
            //    }
            //    dos.close();
            //} catch( IOException e ) {
            //    Console.WriteLine();
            //    System.out.print("Error - cannot open " + fname);
            //    System.exit(1);
            //}

            throw new NotImplementedException();
        }

        public void saveText(String fname)
        {
            //try {
            //    PrintWriter pw = new PrintWriter( new FileOutputStream( fname ) );
            //    pw.println( pop.Length );
            //    pw.println( getGeneCount() );
            //    for( int i = 0; i < pop.Length; ++i ) {
            //        for( int j = 0; j < getGeneCount(); ++j ) {
            //            pw.print( pop[i].weight[j] );
            //        }
            //        pw.println();
            //    }
            //    pw.close();
            //} catch( IOException e ) {
            //    Console.WriteLine();
            //    System.out.print(" Error - cannot open " + fname);
            //    System.exit(1);
            //}

            throw new NotImplementedException();
        }

        public int load(String filename)
        {
            //try {
            //    BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream( filename ) ) ) ;
            //    StreamTokenizer st = new StreamTokenizer( br );
            //    st.nextToken();
            //    pop = new Neuron[(Integer.valueOf(st.toString())).intValue()];
            //    geneCount = (Integer.valueOf(st.toString())).intValue();
            //    for( int i = 0; i < pop.Length; ++i ) {
            //        pop[i] = new Neuron(getGeneCount());
            //        for( int j = 0; j < getGeneCount(); ++j ) {
            //            st.nextToken();
            //            pop[i].weight[j] = Double.valueOf(st.toString()).doubleValue();
            //        }
            //    }
            //    br.close();
            //} catch( IOException e ) {
            //    Console.WriteLine();
            //    System.out.print(" Error - cannot open " + filename);
            //    System.exit(1);
            //}
            //return pop.Length;

            throw new NotImplementedException();
        }

        public void print()
        {
            int wSize = pop[0].weight.Length;
            Console.WriteLine("GENE_SIZE " + wSize);
            for (int i = 0; i < pop.Length; ++i)
            {
                for (int j = 0; j < wSize; ++j)
                {
                    Console.Write(pop[i].weight[j]);
                }
                Console.WriteLine();
            }
        }

        public void printWeight(StreamWriter pw)
        {
            int wSize = pop[0].weight.Length;
            for (int i = 0; i < pop.Length; ++i)
            {
                for (int j = 0; j < wSize; ++j)
                {
                    pw.Write(pop[i].weight[j] + " ");
                }
                //    fprintf(file,"\n");
                pw.WriteLine();
            }
            pw.Close();
        }

//	public void printDelta(File file);

        //private:
        public bool evolvable;

        private int numBreed; //number of neurons to be mated in SubPopulation
//----------------------------------------------------------------------
// 1-point crossover
        private void crossover(double[] parent1, double[] parent2, double[] child1, double[] child2)
        {
            //find crossover point
            int cross1 = Math.Abs(RandomSingleton.getInstance().Next())%getGeneCount();
            Array.Copy(parent2, 0, child1, 0, child1.Length);
            Array.Copy(parent1, 0, child2, 0, child2.Length);

            for (int i = 0; i < cross1; i++)
            {
                double temp = child1[i];
                child1[i] = child2[i];
                child2[i] = temp;
            }
        }

//----------------------------------------------------------------------
// randomly find a mate in the same subpop.
        private int findMate(int num)
        {
            if (num == 0)
            {
                return (Math.Abs(RandomSingleton.getInstance().Next())%numBreed);
            }
            else
            {
                return (Math.Abs(RandomSingleton.getInstance().Next())%num);
            }
        }

        private double[] doubleArrayInsert(double[] array, double value, int position)
        {
            double[] rc = new double[array.Length + 1];
            Array.Copy(array, 0, rc, 0, position);
//		for( int i = 0; i < position; i++ ) {
//			rc[i] = array[i];
//		}
            rc[position] = value;
            Array.Copy(array, position, rc, position + 1, array.Length - position);
//		for( int i = position; i < array.Length; i++ ) {
//			rc[i+1] = array[i];
//		}
            return rc;
        }

        private double[] doubleArrayRemove(double[] array, int position)
        {
            double[] rc = new double[array.Length - 1];
            Array.Copy(array, 0, rc, 0, position);
//		for( int i = 0; i < position; i++ ) {
//			rc[i] = array[i];
//		}
            Array.Copy(array, position + 1, rc, position, array.Length - position - 1);
//		for( int i = position+1; i < array.Length; i++ ) {
//			rc[i-1] = array[i];
//		}
            return rc;
        }
    }
}
